﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Controls.Primitives;
using Bling.DSL;
using Bling.WPF;
using Bling.Util;
using Bling.Physics;
using Bling.Shapes;
using Bling.Geom;

namespace Bling.PhysicsTutorial {
  /// <summary>
  /// Demonstrates how to create a chain using physics. For fun, we use points on the chain to form meta-balls. 
  /// </summary>
  public class Chain : PhysicsCanvas {
    public Chain() : base("Chain", 10) { }
    protected override Action Init(CanvasBl MainCanvas, World World) {
      PointBl r = new Random();
      PointBl P = new PointBl(0, 10);
      // block of thumbs with positions. 
      PositionBlock<Thumb, ThumbBl> block = new PositionBlock<Thumb, ThumbBl>(World, 9, i => {
        return new ThumbBl(MainCanvas) {
          Size = new Point(20,20), Opacity = 0.3,
          //CenterPosition = { Now = new PointBl(500, 500) * r },
          CanDrag = true, Background = i.SelectColor(), ZIndex = 2,
        };
      }) { Name = "Chain", };
      { // Use curved polygon segments to connect chain segments together to form a snake.
        var snake = new PathShapeBl(MainCanvas) {
          ZIndex = 1, Stroke = { Thickness = 8, Brush = Brushes.LightGray }
        };
        // start point is the first thumb.
        snake.Start = block[0].At;
        // The first segment from point 0 does not need to be curved for a previous point, so just use point zero.
        // Otherwise, start at point 0, end at point 1, curved for point 2.
        snake.Segments.Add(GeometryExtensions.PolygonSegment(1.0, block[0].At, block[0].At, block[1].At, block[2].At));
        // after first segment, only need N - 2 more segments
        for (int i = 0; i < (block.Count - 2); i++) {
          var points = new PointBl[4];
          // basically, each segment is curved according to next and previous points. 
          for (int j = 0; j < points.Length; j++) {
            var k = i + j;
            // last segment does not need to be curved for a next point, so just use the last point of the curve.
            k = Math.Min(k, block.Count - 1);
            points[j] = block[k].At;
          }
          snake.Segments.Add(GeometryExtensions.PolygonSegment(1.0, points));
        }
      }
      // Strength of springs between thumbs.
      var Strength = new LabelSliderBl(MainCanvas) {
        Minimum = 0, Maximum = 1, Value = 1, Width = MainCanvas.Width, LeftBottom = new PointBl(0, MainCanvas.Height), ZIndex = 100,
        LabelName = "Strength",
      };
      // Rest length of springs between thumbs.
      var Length = new LabelSliderBl(MainCanvas) {
        Minimum = 1, Maximum = 100, Value = 50, Width = MainCanvas.Width, LeftBottom = Strength.LeftTop, ZIndex = 100,
        LabelName = "Length",
      };

      block.ForAll(body => {
        // Each physical position is initialized to a random.
        body.Position().Init = r * new PointBl(500,500);
        // Establish a link with the thumb so that the thumb's position is updated to the physical position unless the thumb
        // is being dragged, in which case the physical position is updated to the thumb's position.
        body.Position().Link[!body.At.IsDragging] = body.At.CenterPosition;
        // Verlet step to add momentum to each thumb, loss/friction of 1%.
        body.Position().Step = (now, old) => now.VerletStep(0.01, old);
        for (int i = 0; i < 1; i++) {
          // a pair of constraints that implements a spring between each thumb.
          body.Position().Relax[.5d * Strength.Value, body.Index > 0] = 
            body.Position().Spring(body.Prev[1].Position(), Length.Value);
          body.Position().Relax[.5d * Strength.Value, body.Index < block.Count - 1] = 
            body.Position().Spring(body.Next[1].Position(), Length.Value);
        }
        // As with sliders, restitution and clamping.
        //body.Position().Old = body.Position().Old.Restitution(body.Position(), new Point(0, 0), MainCanvas.Size);
        //body.Position().Bind = body.Position().Clamp(new Point(0, 0), MainCanvas.Size);
        body.Position().ClampWithRestitution(new PointBl(0, 0), MainCanvas.Size);
      });

      var overlay = new CanvasBl(MainCanvas) {
        ZIndex = 0, Size = MainCanvas.Size, Background = Colors.LightGray,
        // Just for fun, we add a meta-ball effect to the simulation. This will only work for at most 9 links,
        // otherwise the pixel shader is too complicated and will be rejected. 
        Effect = {
          Custom = (input, uv) => {
            ColorBl color = Colors.Black;
            DoubleBl metaball = 0;
            var sz = MainCanvas.Size;
            for (int i = 0; i < block.Count; i++) {
              PointBl point = block[i].At;
              var v = (uv * sz - point);
              v = v * v;
              var at = 3d / (v.X + v.Y);
              color += i.SelectColor() * at;
              metaball += at;
            }
            var useColor = color / metaball;
            return (metaball > .0032).Condition(useColor, Colors.White);
          }
        },
      };
      return () => { };
    }
  }
}
    


